package com.kingsoft.dc.khaos.dsl.utils;

import org.apache.commons.io.FileUtils;

import java.io.Serializable;
import java.io.File;
import java.io.IOException;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.InputStreamReader;


import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentSkipListMap;
import java.util.concurrent.locks.ReentrantLock;

public class IPUtil implements Serializable {
    private int offset;
    private int[] index = new int[65536];
    private ByteBuffer dataBuffer;
    private ByteBuffer indexBuffer;
    private File ipFile;
    private File aclFile;
    private File lineFile;
    private ConcurrentSkipListMap<Long, String> ipMapping = new ConcurrentSkipListMap<Long, String>();
    private ConcurrentSkipListMap<Long, String> ipSingleMapping = new ConcurrentSkipListMap<Long, String>();
    private ReentrantLock lock = new ReentrantLock();

    private static IPUtil ipUtil;
    private static String synStr = "IPUtil init syn";

    private IPUtil() throws IOException {
//        ipFile = new File(IPUtil.class.getClassLoader()
//                .getResource("mydata4vipday2.datx").getFile());
//        aclFile = new File(IPUtil.class.getClassLoader().getResource("ksacl")
//                .getFile());
//        lineFile = new File(IPUtil.class.getClassLoader()
//                .getResource("geo_line.json").getFile());
        String linePath = "";
        String aclPath = "";
        String dbPath = "";
        String tmpDir = System.getProperties().getProperty("java.io.tmpdir");
        linePath = tmpDir + "geo_line.json";
        aclPath = tmpDir + "ksacl";
        dbPath = tmpDir + "mydata4vipday2.datx";
        lineFile = new File(linePath);
        ipFile = new File(dbPath);
        aclFile = new File(aclPath);
        FileUtils.copyInputStreamToFile(this.getClass().getClassLoader().getResourceAsStream("geo_line.json"), lineFile);
        FileUtils.copyInputStreamToFile(this.getClass().getClassLoader().getResourceAsStream("ksacl"), aclFile);
        FileUtils.copyInputStreamToFile(this.getClass().getClassLoader().getResourceAsStream("mydata4vipday2.datx"), ipFile);

        load();
        lineLoad();
    }

    public static synchronized IPUtil getInstance() throws IOException {
        if (null == ipUtil) {
            ipUtil = new IPUtil();
        }
        return ipUtil;
    }

    /**
     * 查询IP所在线路
     *
     * @param ip
     * @return
     */
    public String findLine(String ip) {
        try {
            long code = ip2long(ip);
            if (ipSingleMapping.containsKey(code))
                return ipSingleMapping.get(code);
            Long min = ipMapping.floorKey(code);
            Long max = ipMapping.ceilingKey(code);
            if (min != null && max != null) {
                String minArea = ipMapping.get(min);
                String maxArea = ipMapping.get(max);
                if (minArea.equals(maxArea))
                    return minArea;
            }
        } catch (Exception e) {
        }
        return "全网默认";
    }

    public String[] getRemoteIpInfos(String ip) {
        String[] result = new String[]{"", "", "", ""};
        if (ip.equals("-"))
            return result;
        try {
            String[] ipInfos = findInfo(ip);
            result[0] = ipInfos[0];
            result[1] = ipInfos[1];
            result[2] = ipInfos[2];
            result[3] = ipInfos[4];
            // if ("中国".equals(ipInfos[0])) {
            // result[0] = ipInfos[1] + ipInfos[4];
            // } else {
            // result[0] = ipInfos[0];
            // }
            //
            // if ("中国".equals(ipInfos[0])) {
            // if (ipInfos[1].equals(ipInfos[2]))
            // result[1] = ipInfos[1] + ipInfos[4];
            // result[1] = ipInfos[1] + ipInfos[2] + ipInfos[4];
            // } else {
            // if (!ipInfos[0].equals(ipInfos[1]))
            // result[1] = ipInfos[0] + ipInfos[1];
            // else
            // result[1] = ipInfos[0];
            // }
        } catch (Exception e) {
        }
        return result;
    }

    public String[] getRebackIpInfos(String ip) {
        String[] result = new String[]{ip};
        if (ip.equals("-"))
            return result;
        try {
            String[] ipInfos = findInfo(ip);

            if ("中国".equals(ipInfos[0])) {
                if (ipInfos[1].equals(ipInfos[2]))
                    result[0] = ipInfos[1] + ipInfos[4];
                result[0] = ipInfos[1] + ipInfos[2] + ipInfos[4];
            } else {
                if (!ipInfos[0].equals(ipInfos[1]))
                    result[0] = ipInfos[0] + ipInfos[1] + ipInfos[4];
                else
                    result[0] = ipInfos[0] + ipInfos[4];
            }
        } catch (Exception e) {
        }
        return result;
    }

    private void lineLoad() {
        Map<String, String> nameMapping = loadJsonNameMapping();
        Map<String, List<String>> ipRangeMapping = loadAclMapping();

        ConcurrentSkipListMap<Long, String> current = new ConcurrentSkipListMap<Long, String>();
        ConcurrentSkipListMap<Long, String> singleCurrent = new ConcurrentSkipListMap<Long, String>();

        for (String line : ipRangeMapping.keySet()) {
            if (!nameMapping.containsKey(line)) {
                throw new RuntimeException("线上不存在［" + line + "］线路");
            }
            String lineCh = nameMapping.get(line);
            List<String> ipAndMasks = ipRangeMapping.get(line);
            for (String ipAndMask : ipAndMasks) {
                String[] ipArr = ipAndMask.split("/");
                int netMask = Integer.valueOf(ipArr[1].trim());
                long ipStart = ip2long(ipArr[0]);
                if (netMask >= 28) {
                    for (int i = 0; i < Math.pow(2.0, 32.0 - netMask); i++) {
                        singleCurrent.put(ipStart + i, lineCh);
                    }
                } else {
                    current.put(ipStart, lineCh);
                    current.put(ipStart + (long) Math.pow(2.0, 32.0 - netMask) - 1,
                            lineCh);
                }
            }
        }

        ConcurrentSkipListMap<Long, String> currentNew = new ConcurrentSkipListMap<Long, String>();
        Long[] codes = new Long[current.size()];
        current.keySet().toArray(codes);
        boolean hasBegin = false;
        for (int i = 0; i < codes.length - 2; i++) {
            if (!hasBegin) {
                currentNew.put(codes[i], current.get(codes[i]));
                hasBegin = true;
            }
            if (!current.get(codes[i + 1]).equals(current.get(codes[i + 2]))) {
                currentNew.put(codes[i + 1], current.get(codes[i + 1]));
                hasBegin = false;
            }
            i++;
        }

        if (currentNew.size() > 0) {
            ipMapping = currentNew;
            ipSingleMapping = singleCurrent;
        } else {
            throw new RuntimeException("IPUtil load error");
        }

    }

    private Map<String, List<String>> loadAclMapping() {
        BufferedReader reader = null;
        StringBuffer laststr = new StringBuffer("{");
        FileInputStream fileInputStream = null;
        InputStreamReader inputStreamReader = null;
        try {
            fileInputStream = new FileInputStream(aclFile);
            inputStreamReader = new InputStreamReader(
                    fileInputStream, "UTF-8");
            reader = new BufferedReader(inputStreamReader);
            String tempString = null;
            int status = 0;
            int lineNum = 0;
            while ((tempString = reader.readLine()) != null) {
                tempString = tempString.trim();
                lineNum++;
                if (tempString.startsWith("acl")) {
                    if (status == 0)
                        tempString = tempString.replace("acl", "").trim()
                                .replace("{", ":[");
                    else if (status == 3)
                        tempString = tempString.replace("acl", ",").trim()
                                .replace("{", ":[");
                    else
                        throw new Exception("格式异常,行号:" + lineNum + ","
                                + tempString);
                    status = 1;
                } else if (tempString.startsWith("}")) {
                    if (status == 2)
                        tempString = "]";
                    else
                        throw new Exception("格式异常,行号:" + lineNum + ","
                                + tempString);
                    status = 3;
                } else if (!tempString.equals("")) {
                    if (status == 2)
                        tempString = ",\"" + tempString.replace(";", "") + "\"";
                    else if (status == 1)
                        tempString = "\"" + tempString.replace(";", "") + "\"";
                    else
                        throw new Exception("格式异常,行号:" + lineNum + ","
                                + tempString);
                    status = 2;
                }
                laststr.append(tempString);
            }
            laststr.append("}");
            Map<String, List<String>> ipRangeMapping = JsonUtils.json2object(
                    laststr.toString(), Map.class);
            return ipRangeMapping;
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            if (reader != null) {
                try {
                    reader.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (fileInputStream != null) {
                try {
                    fileInputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (inputStreamReader != null) {
                try {
                    inputStreamReader.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    @SuppressWarnings("unchecked")
    private Map<String, String> loadJsonNameMapping() {
        BufferedReader reader = null;
        StringBuffer laststr = new StringBuffer();
        FileInputStream fileInputStream = null;
        InputStreamReader inputStreamReader = null;
        try {
            fileInputStream = new FileInputStream(lineFile);
            inputStreamReader = new InputStreamReader(
                    fileInputStream, "UTF-8");
            reader = new BufferedReader(inputStreamReader);
            String tempString = null;
            while ((tempString = reader.readLine()) != null) {
                laststr.append(tempString);
            }
            Map<String, Object> geoline = JsonUtils.json2object(
                    laststr.toString(), Map.class);
            Map<String, String> nameMapping = new HashMap<String, String>();
            List<Map<String, Object>> lines = (List<Map<String, Object>>) geoline
                    .get("geolocations");
            for (Map<String, Object> line : lines) {
                nameMapping.put((String) line.get("name"),
                        (String) line.get("description"));
            }
            return nameMapping;
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            if (reader != null) {
                try {
                    reader.close();
                } catch (IOException e) {
                }
            }
            if (fileInputStream != null) {
                try {
                    fileInputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (inputStreamReader != null) {
                try {
                    inputStreamReader.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    private String[] findInfo(String ip) {
        String[] ips = ip.split("\\.");
        int prefix_value = (Integer.valueOf(ips[0]) * 256 + Integer
                .valueOf(ips[1]));
        long ip2long_value = ip2long(ip);
        int start = index[prefix_value];
        int max_comp_len = offset - 262144 - 4;
        long tmpInt;
        long index_offset = -1;
        int index_length = -1;
        byte b = 0;
        for (start = start * 9 + 262144; start < max_comp_len; start += 9) {
            tmpInt = int2long(indexBuffer.getInt(start));
            if (tmpInt >= ip2long_value) {
                index_offset = bytesToLong(b, indexBuffer.get(start + 6),
                        indexBuffer.get(start + 5), indexBuffer.get(start + 4));
                index_length = (0xFF & indexBuffer.get(start + 7) << 8)
                        + (0xFF & indexBuffer.get(start + 8));
                break;
            }
        }

        byte[] areaBytes;

        lock.lock();
        try {
            dataBuffer.position(offset + (int) index_offset - 262144);
            areaBytes = new byte[index_length];
            dataBuffer.get(areaBytes, 0, index_length);
        } finally {
            lock.unlock();
        }

        return new String(areaBytes, Charset.forName("UTF-8")).split("\t", -1);
    }

    private void load() {
        lock.lock();
        try {
            dataBuffer = ByteBuffer.wrap(getBytesByFile(ipFile));
            offset = dataBuffer.getInt(); // indexLength
            byte[] indexBytes = new byte[offset];
            dataBuffer.get(indexBytes, 0, offset - 4);
            indexBuffer = ByteBuffer.wrap(indexBytes);
            indexBuffer.order(ByteOrder.LITTLE_ENDIAN);
            for (int i = 0; i < 256; i++) {
                for (int j = 0; j < 256; j++) {
                    index[i * 256 + j] = indexBuffer.getInt();
                }
            }
            indexBuffer.order(ByteOrder.BIG_ENDIAN);
        } finally {
            lock.unlock();
        }

    }

    private byte[] getBytesByFile(File file) {
        FileInputStream fin = null;
        byte[] bs = new byte[(int)(file.length())];
        try {
            fin = new FileInputStream(file);
            int readBytesLength = 0;
            int i;
            while ((i = fin.available()) > 0) {
                fin.read(bs, readBytesLength, i);
                readBytesLength += i;
            }
        } catch (IOException ioe) {
            ioe.printStackTrace();
        } finally {
            try {
                if (fin != null) {
                    fin.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        return bs;
    }

    private long bytesToLong(byte a, byte b, byte c, byte d) {
        return int2long((((a & 0xff) << 24) | ((b & 0xff) << 16)
                | ((c & 0xff) << 8) | (d & 0xff)));
    }

    private int str2Ip(String ip) {
        String[] ss = ip.split("\\.");
        int a, b, c, d;
        a = Integer.parseInt(ss[0]);
        b = Integer.parseInt(ss[1]);
        c = Integer.parseInt(ss[2]);
        d = Integer.parseInt(ss[3]);
        return (a << 24) | (b << 16) | (c << 8) | d;
    }

    private long ip2long(String ip) {
        return int2long(str2Ip(ip));
    }

    private long int2long(int i) {
        long l = i & 0x7fffffffL;
        if (i < 0) {
            l |= 0x080000000L;
        }
        return l;
    }

    public static void main(String[] args) throws IOException {
        IPUtil ipUtil = IPUtil.getInstance();
        String ip = "58.222.33.19";
        System.out.println(ipUtil.findLine(ip));
        String[] ipInfos = ipUtil.getRemoteIpInfos(ip);
        String remoteIpState = ipInfos[0];
        String remoteIpProvince = ipInfos[1];
        String remoteIpCity = ipInfos[2];
        System.out.println(remoteIpState + "\t" + remoteIpProvince + "\t" + remoteIpCity);
    }
}